/**
 * PlanGenerator 属性测试
 * 
 * 使用 fast-check 进行属性测试，验证计划生成器的正确性
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fc from 'fast-check';
import { PlanGenerator } from './PlanGenerator';
import { GitCodeApiService } from './GitCodeApiService';
import type { DetectedTask, TaskPriority, TaskPlan } from '../types';

// ============================================
// 生成器定义
// ============================================

/**
 * 生成任务优先级
 */
const taskPriorityArbitrary: fc.Arbitrary<TaskPriority> = fc.constantFrom('high', 'medium', 'low');

/**
 * 生成有效的参与者名称
 */
const participantArbitrary = fc.string({ minLength: 1, maxLength: 15 })
  .filter(s => {
    const trimmed = s.trim();
    return trimmed.length > 0 && !trimmed.includes('\n');
  })
  .map(s => s.trim());

/**
 * 生成任务描述
 */
const taskDescriptionArbitrary = fc.string({ minLength: 5, maxLength: 100 })
  .filter(s => s.trim().length >= 5)
  .map(s => s.trim());

/**
 * 生成单参与者的 DetectedTask
 */
const singleParticipantTaskArbitrary: fc.Arbitrary<DetectedTask> = fc.record({
  id: fc.uuid(),
  description: taskDescriptionArbitrary,
  participants: fc.array(participantArbitrary, { minLength: 1, maxLength: 1 }),
  priority: taskPriorityArbitrary,
  deadline: fc.option(fc.date({ min: new Date(), max: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) }), { nil: undefined })
});

/**
 * 生成多参与者的 DetectedTask
 */
const multiParticipantTaskArbitrary: fc.Arbitrary<DetectedTask> = fc.record({
  id: fc.uuid(),
  description: taskDescriptionArbitrary,
  participants: fc.array(participantArbitrary, { minLength: 2, maxLength: 5 }).map(arr => [...new Set(arr)]).filter(arr => arr.length >= 2),
  priority: taskPriorityArbitrary,
  deadline: fc.option(fc.date({ min: new Date(), max: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) }), { nil: undefined })
});

/**
 * 生成带截止时间的 DetectedTask
 */
const taskWithDeadlineArbitrary: fc.Arbitrary<DetectedTask> = fc.record({
  id: fc.uuid(),
  description: taskDescriptionArbitrary,
  participants: fc.array(participantArbitrary, { minLength: 1, maxLength: 5 }).map(arr => [...new Set(arr)]),
  priority: taskPriorityArbitrary,
  deadline: fc.date({ min: new Date(), max: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) })
});

/**
 * 生成任意 DetectedTask
 */
const detectedTaskArbitrary: fc.Arbitrary<DetectedTask> = fc.oneof(
  singleParticipantTaskArbitrary,
  multiParticipantTaskArbitrary,
  taskWithDeadlineArbitrary
);

/**
 * 步骤类型定义
 */
interface MockStep {
  order: number;
  description: string;
  assignee: string | undefined;
  estimatedTime: string | undefined;
}

/**
 * 生成模拟的 LLM 计划响应
 */
const mockPlanResponseArbitrary = (task: DetectedTask): fc.Arbitrary<string> => {
  return fc.array(
    fc.record({
      order: fc.integer({ min: 1, max: 10 }),
      description: fc.string({ minLength: 5, maxLength: 50 }).filter(s => s.trim().length >= 5).map(s => s.trim()),
      assignee: fc.option(fc.constantFrom(...task.participants), { nil: undefined }),
      estimatedTime: fc.option(fc.constantFrom('10分钟', '30分钟', '1小时', '2小时'), { nil: undefined }),
    }),
    { minLength: 1, maxLength: 7 }
  ).map((steps: MockStep[]) => {
    // 确保步骤顺序正确
    const orderedSteps = steps.map((step: MockStep, i: number) => ({
      ...step,
      order: i + 1,
      // 如果任务有多个参与者，确保至少一个步骤有 assignee
      assignee: task.participants.length > 1 
        ? (step.assignee || task.participants[i % task.participants.length])
        : undefined,
      // 如果任务有截止时间，最后一步添加 deadline
      deadline: task.deadline && i === steps.length - 1 
        ? task.deadline.toISOString() 
        : undefined
    }));

    return JSON.stringify({
      title: `${task.description} 计划`,
      summary: `这是关于 ${task.description} 的执行计划`,
      steps: orderedSteps
    });
  });
};

// ============================================
// 测试辅助函数
// ============================================

/**
 * 创建带有模拟 API 的 PlanGenerator
 */
function createMockedGenerator(mockResponse: string): PlanGenerator {
  const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
  vi.spyOn(mockApiService, 'chat').mockResolvedValue(mockResponse);
  return new PlanGenerator(mockApiService);
}

// ============================================
// Property 5: 计划完整性
// ============================================

describe('PlanGenerator Property Tests', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  /**
   * **Feature: smart-chat-reply, Property 5: 计划完整性**
   * **Validates: Requirements 4.2, 4.3, 4.4, 4.5**
   * 
   * 对于任意检测到的任务，PlanGenerator.generate() 应返回包含以下内容的计划：
   * - 非空的 steps 数组
   * - 如果任务有多个参与者，至少一个步骤应包含 assignee
   * - 如果任务有 deadline，计划中应包含时间节点
   * - 非空的 shareableText 字段
   */
  describe('Property 5: 计划完整性', () => {
    it('生成的计划应包含非空的 steps 数组', async () => {
      await fc.assert(
        fc.asyncProperty(
          detectedTaskArbitrary,
          async (task) => {
            // 为每个任务生成对应的模拟响应
            const mockResponse = await fc.sample(mockPlanResponseArbitrary(task), 1)[0];
            const generator = createMockedGenerator(mockResponse);
            
            const plan = await generator.generate(task);
            
            // 验证 steps 是非空数组
            expect(Array.isArray(plan.steps)).toBe(true);
            expect(plan.steps.length).toBeGreaterThan(0);
            
            // 验证每个步骤都有必要字段
            for (const step of plan.steps) {
              expect(step.order).toBeDefined();
              expect(typeof step.order).toBe('number');
              expect(step.description).toBeDefined();
              expect(typeof step.description).toBe('string');
              expect(step.description.length).toBeGreaterThan(0);
            }
          }
        ),
        { numRuns: 100 }
      );
    });

    it('多参与者任务的计划应包含 assignee', async () => {
      await fc.assert(
        fc.asyncProperty(
          multiParticipantTaskArbitrary,
          async (task) => {
            // 为多参与者任务生成模拟响应
            const mockResponse = await fc.sample(mockPlanResponseArbitrary(task), 1)[0];
            const generator = createMockedGenerator(mockResponse);
            
            const plan = await generator.generate(task);
            
            // 验证至少一个步骤包含 assignee
            // Requirements: 4.3
            const hasAssignee = plan.steps.some(step => step.assignee !== undefined && step.assignee.length > 0);
            expect(hasAssignee).toBe(true);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('带截止时间的任务计划应包含时间节点', async () => {
      await fc.assert(
        fc.asyncProperty(
          taskWithDeadlineArbitrary,
          async (task) => {
            // 为带截止时间的任务生成模拟响应
            const mockResponse = await fc.sample(mockPlanResponseArbitrary(task), 1)[0];
            const generator = createMockedGenerator(mockResponse);
            
            const plan = await generator.generate(task);
            
            // 验证计划中包含时间节点（在步骤中或 shareableText 中）
            // Requirements: 4.4
            const hasDeadlineInSteps = plan.steps.some(step => step.deadline !== undefined);
            const hasDeadlineInText = plan.shareableText.includes('截止');
            
            expect(hasDeadlineInSteps || hasDeadlineInText).toBe(true);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('生成的计划应包含非空的 shareableText', async () => {
      await fc.assert(
        fc.asyncProperty(
          detectedTaskArbitrary,
          async (task) => {
            // 为任务生成模拟响应
            const mockResponse = await fc.sample(mockPlanResponseArbitrary(task), 1)[0];
            const generator = createMockedGenerator(mockResponse);
            
            const plan = await generator.generate(task);
            
            // 验证 shareableText 非空
            // Requirements: 4.5
            expect(plan.shareableText).toBeDefined();
            expect(typeof plan.shareableText).toBe('string');
            expect(plan.shareableText.length).toBeGreaterThan(0);
          }
        ),
        { numRuns: 100 }
      );
    });

    it('API 错误时应返回备用计划而非抛出异常', async () => {
      await fc.assert(
        fc.asyncProperty(
          detectedTaskArbitrary,
          async (task) => {
            const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
            vi.spyOn(mockApiService, 'chat').mockRejectedValue(new Error('API Error'));
            const generator = new PlanGenerator(mockApiService);
            
            // 不应抛出异常
            let plan: TaskPlan | null = null;
            let error: Error | null = null;
            
            try {
              plan = await generator.generate(task);
            } catch (e) {
              error = e as Error;
            }
            
            // 验证没有抛出异常
            expect(error).toBeNull();
            // 验证返回了有效的计划
            expect(plan).not.toBeNull();
            expect(plan!.steps.length).toBeGreaterThan(0);
            expect(plan!.shareableText.length).toBeGreaterThan(0);
          }
        ),
        { numRuns: 50 }
      );
    });

    it('format() 方法应返回 shareableText', async () => {
      await fc.assert(
        fc.asyncProperty(
          detectedTaskArbitrary,
          async (task) => {
            const mockResponse = await fc.sample(mockPlanResponseArbitrary(task), 1)[0];
            const generator = createMockedGenerator(mockResponse);
            
            const plan = await generator.generate(task);
            const formatted = generator.format(plan);
            
            // format() 应返回 shareableText
            expect(formatted).toBe(plan.shareableText);
          }
        ),
        { numRuns: 50 }
      );
    });
  });
});
